/*******************************************************************************

      ---------========= DSx86 Configuration Editor =========---------
                               by RyouArashi
                        http://ryouarashi.blogspot.com


              ---------========= License Agreement =========---------

Copyright 2010 RyouArashi. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

   1. Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.

   2. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY RyouArashi ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL RyouArashi OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*******************************************************************************/

/*******************************************************************************
 * DSx86 INI-File Parser
 ******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>

#include "INIFILE.H"
#include "UTIL.H"

/* Config File Names **********************************************************/
const char * ConfigFileName[] =
{
  "C:\\data\\dsx86\\dsx86.ini",
  "dsx86.ini",
  NULL
};

/* Location of Config File (Index in array above) *****************************/
unsigned ConfigFileIndex;

/* Temp File Name (inside current directory) **********************************/
const char ConfigTempName[] =
  "dsx86cfg.tmp";

/* Parser Line Buffers ********************************************************/
char LineBuffer[LINE_BUFLEN+1];
char KeyBuffer [LINE_BUFLEN+1];
char ValBuffer [LINE_BUFLEN+1];

/* Functions ******************************************************************/

/*
  Parse Config File
*/
struct ProfileList * ParseConfigFile()
{
  /* Files */
  FILE               * FIn   = NULL;
  FILE               * FTemp = NULL;

  /* List */
  struct ProfileList * Ret   = NULL;

  /* Parser State */
  enum {BEFORE_SEC, DSX86_SEC, PROG_SEC} State = BEFORE_SEC;
  unsigned Line = 0;
  unsigned KeyIgnored = 0;

  /* Find Config File */
  cprintf("\r\nOpening Config File");
  ConfigFileIndex = 0;
  while (!FIn && ConfigFileName[ConfigFileIndex])
  {
    cprintf("\r\nTrying %s...", ConfigFileName[ConfigFileIndex]);
    FIn = fopen(ConfigFileName[ConfigFileIndex], "r");
    if (!FIn) ConfigFileIndex++;
  }

  /* Check if file was found */
  if (!FIn)
  {
    cprintf(
      "\r\n"
      "\r\nError: Cannot find/open Config File!"
      "\r\nPut dsx86.ini into one of the places"
      "\r\nabove and restart."
      "\r\n");
    goto exit;
  }

  /* Create Temp File */
  FTemp = fopen(ConfigTempName, "w");
  if (!FTemp)
  {
     perror(
       "\r\n"
       "\r\nError creating Temp File:"
       "\r\n");
     goto exit;
  }

  /* File was found */
  cprintf("OK!\r\nParsing File...");

  /* Create Profile List */
  Ret = ProfileList_Create();
  if (!Ret)
  {
    cprintf("\r\n\r\nError: Out of Memory!\r\n\r\n");
    goto exit;
  }

  /* Parse File */
  while(fgets(LineBuffer, LINE_BUFLEN, FIn))
  {
    unsigned j, len;
    int      comment;

    /* Count Line */
    Line++;

    /* Check input Line*/
    comment = -1;
    for (j = 0; j < LINE_BUFLEN; j++)
    {
      /* Remember Comment start position */
      if (LineBuffer[j] == ';')
      {
        comment = j;
      }
      if (LineBuffer[j] == '\n' || LineBuffer[j] == '\r')
      {
        break;
      }
    }
    if (j == LINE_BUFLEN)
    {
      cprintf("\r\n\r\nError reading Config File:\r\n");
      cprintf("Input Line too Long!");
      goto error;
    }

    /* Set length of Input line */
    len = (comment >= 0) ? (unsigned)comment : j;

    /* Skip any blanks before Line End */
    while(len && (LineBuffer[len-1] == ' ' || LineBuffer[len-1] == 9)) len--;

    /* Write Line to Temp File */
    if(!fputs(LineBuffer, FTemp))
    {
      break;
    }

    /* Skip Whitespaces */
    j = 0;
    while(LineBuffer[j] == ' ') j++;

    /* Skip Comment and empty Lines */
    if (LineBuffer[j] == ';' || LineBuffer[j] == '\n' || LineBuffer[j] == '\r')
    {
      continue;
    }

    /* Check Beginning of Section */
    if (LineBuffer[j] == '[')
    {
      /* Check Syntax */
      char * t = strchr(LineBuffer+j, ']');
      if (!t)
      {
        cprintf("\r\n\r\nError reading Config File:\r\n");
        cprintf("Syntax Error on Line %u:\r\n", Line);
        cprintf("Missing ']' in Section Header!");
        goto error;
      }
      if (t - LineBuffer+j-1 > MAX_NAME)
      {
        cprintf("\r\n\r\nError reading Config File:\r\n");
        cprintf("Syntax Error on Line %u:\r\n", Line);
        cprintf("Section Name too long!");
        goto error;
      }

      /* DSx86 section must come first */
      if (State == BEFORE_SEC)
      {
        if (nc_strncmp(LineBuffer+j, "[DSx86]", sizeof("[DSx86]")-1))
        {
          cprintf("\r\n\r\nError reading Config File:\r\n");
          cprintf("Line %u:\r\n", Line);
          cprintf("First Section must be [DSx86] !");
          goto error;
        }
        State = DSX86_SEC;
      }
      /* Open new Section */
      else
      {
        /* Create new Section in List*/
        struct Profile * New;

        /* Append Profile */
        New = ProfileList_Append(Ret);

        /* Check Result */
        if (!New)
        {
          cprintf("\r\n\r\nError: Out of Memory!\r\n\r\n");
          goto error;
        }

        /* Init Section */
        memset(New, 0, sizeof(struct Profile));
        strncpy(New->Name, LineBuffer+j+1, t-1-LineBuffer+j);
        New->Name[MAX_NAME] = '\0';

        /* Next State */
        State = PROG_SEC;
      }

    }
    /* Parse Section Entry */
    else
    {
      /* Error if not inside section yet */
      if (State == BEFORE_SEC)
      {
        cprintf("\r\n\r\nError reading Config File:\r\n");
        cprintf("Syntax Error on Line %u:\r\n", Line);
        cprintf("Encountered Key outside Section!");
        goto error;
      }
      /* Parse Section Entry*/
      else
      {
        char * t = strchr(LineBuffer+j, '=');

        /* Check Syntax */
        if (!t)
        {
          cprintf("\r\n\r\nError reading Config File:\r\n");
          cprintf("Syntax Error on Line %u:\r\n", Line);
          cprintf("<Key>=<Value> expected!");
          goto error;
        }

        /* Copy Key and Value */
        strncpy(KeyBuffer, LineBuffer+j, t-LineBuffer-j);
        KeyBuffer[t-LineBuffer-j] = 0;
        strncpy(ValBuffer, t+1, LineBuffer+len-t-1);
        ValBuffer[LineBuffer+len-t-1] = 0;

        /* Parse [DSx86] Section*/
        if (State == DSX86_SEC)
        {
          if (nc_strcmp(KeyBuffer, "LogFile") == 0)
          {
            strcpy(Ret->LogFile, ValBuffer);
          }
          else
          {
            cprintf("\r\n\r\nError reading Config File:\r\n");
            cprintf("Line %u:\r\n", Line);
            cprintf("Unknown Key \"%s\"!", KeyBuffer);
            goto error;
          }
        }
        /* Parse Profile Section */
        else
        {
          struct Profile * P;
          unsigned OK;
          unsigned k;

          /* Get Current Section*/
          P = Ret->Items[Ret->Count-1];

          /* OK flag */
          OK = 0;

          /* Parse Fields: KEY Bindings */
          for (k = 0; k < NUM_NDSKEYS; k++)
          {
            if (nc_strcmp(KeyBuffer, NDS_Keys[k]) == 0)
            {
              /* Add Key to Section */
              unsigned temp;

              P->KeyFlags |= (1 << k);
              if (!HexToInt(ValBuffer, &temp))
              {
                cprintf("\r\n\r\nError reading Config File:\r\n");
                cprintf("Syntax Error on Line %u:\r\n", Line);
                cprintf("Hexandezimal Value expected\r\nor Value to large!");
                goto error;
              }

              /* Set OK Flag and break */
              P->Keys[k]    = temp        & (unsigned)0x00FF;
              P->ExtKeys[k] = (temp >> 8) & (unsigned)0x00FF;

              OK = 1;
              break;
            }
          }
          /* Parse Fields: Options */
          if (!OK)
          {
            if (nc_strcmp(KeyBuffer, "ScreenScale") == 0)
            {
              /* Find Value */
              for (k = 1; k < NUM_SCALE; k++)
              {
                if (nc_strcmp(ValBuffer, Scale_Modes[k]) == 0)
                {
                  break;
                }
              }
              if (k == NUM_SCALE)
              {
                cprintf("\r\n\r\nError reading Config File:\r\n");
                cprintf("Syntax Error on Line %u:\r\n", Line);
                cprintf("Unknown Value \"%s\"!", ValBuffer);
                goto error;
              }
              else
              {
                P->ScaleMode = k;
                OK = 1;
              }
            }
            else if (nc_strcmp(KeyBuffer, "ScreenUpdate") == 0)
            {
              /* Find Value */
              for (k = 1; k < NUM_UPDATE; k++)
              {
                if (nc_strcmp(ValBuffer, Update_Modes[k]) == 0)
                {
                  break;
                }
              }
              if (k == NUM_UPDATE)
              {
                cprintf("\r\n\r\nError reading Config File:\r\n");
                cprintf("Syntax Error on Line %u:\r\n", Line);
                cprintf("Unknown Value \"%s\"!", ValBuffer);
                goto error;
              }
              else
              {
                /* Change "Direct" to "30FPS" */
                P->UpdateMode = (k == UPDATE_DIRECT ? UPDATE_30FPS : k);
                OK = 1;
              }
            }
            else if (nc_strcmp(KeyBuffer, "TPMScroll") == 0)
            {
              /* Find Value */
              for (k = 0; k < 2; k++)
              {
                if (nc_strcmp(ValBuffer, Boolean_Modes[k]) == 0)
                {
                  break;
                }
              }
              if (k == 2)
              {
                cprintf("\r\n\r\nError reading Config File:\r\n");
                cprintf("Syntax Error on Line %u:\r\n", Line);
                cprintf("Unknown Value \"%s\"!", ValBuffer);
                goto error;
              }
              else
              {
                P->TPMScroll  = k;
                P->TPMFlags  |= (1 << TPM_SCROLL);

                OK = 1;
              }
            }
            else if (nc_strcmp(KeyBuffer, "TPMTap") == 0)
            {
              /* Find Value */
              for (k = 0; k < 2; k++)
              {
                if (nc_strcmp(ValBuffer, Boolean_Modes[k]) == 0)
                {
                  break;
                }
              }
              if (k == 2)
              {
                cprintf("\r\n\r\nError reading Config File:\r\n");
                cprintf("Syntax Error on Line %u:\r\n", Line);
                cprintf("Unknown Value \"%s\"!", ValBuffer);
                goto error;
              }
              else
              {
                P->TPMTap    = k;
                P->TPMFlags |= (1 << TPM_TAP);

                OK = 1;
              }
            }
            else if (nc_strcmp(KeyBuffer, "TPMLeft") == 0)
            {
              /* Find Value */
              for (k = 0; k < NUM_NDSKEYS; k++)
              {
                if (nc_strcmp(ValBuffer, NDS_Keys[k]) == 0)
                {
                  break;
                }
              }
              if (k == NUM_NDSKEYS)
              {
                cprintf("\r\n\r\nError reading Config File:\r\n");
                cprintf("Syntax Error on Line %u:\r\n", Line);
                cprintf("Unknown Value \"%s\"!", ValBuffer);
                goto error;
              }
              else
              {
                P->TPMLeft   = k;
                P->TPMFlags |= (1 << TPM_LEFT);

                OK = 1;
              }
            }
            else if (nc_strcmp(KeyBuffer, "TPMRight") == 0)
            {
              /* Find Value */
              for (k = 0; k < NUM_NDSKEYS; k++)
              {
                if (nc_strcmp(ValBuffer, NDS_Keys[k]) == 0)
                {
                  break;
                }
              }
              if (k == NUM_NDSKEYS)
              {
                cprintf("\r\n\r\nError reading Config File:\r\n");
                cprintf("Syntax Error on Line %u:\r\n", Line);
                cprintf("Unknown Value \"%s\"!", ValBuffer);
                goto error;
              }
              else
              {
                P->TPMRight  = k;
                P->TPMFlags |= (1 << TPM_RIGHT);

                OK = 1;
              }
            }
            else
            {
              /* Any other Key: Ignore and print warning */
              OK = 1;
              KeyIgnored = 1;
            }
          }

          /* No longer needed, as OK is always true now
          if (!OK)
          {
            cprintf("\r\n\r\nError reading Config File:\r\n");
            cprintf("Line %u:\r\n", Line);
            cprintf("Unknown Key \"%s\"!", KeyBuffer);
            goto error;
          }
          */
        }
      }
    }
  }

  /* Check Result */
  if (ferror(FIn))
  {
    perror("\r\n\r\nError reading Config File:\r\n");
    goto error;
  }
  else if (ferror(FTemp))
  {
    perror("\r\n\r\nError writing Temp File:\r\n");
    goto error;
  }
  else if (State == BEFORE_SEC)
  {
    cprintf("\r\n\r\nError: Missing [DSx86] Section!\r\n");
    goto error;
  }
  else
  {
    /* Check for [default] section */
    unsigned j;

    for(j = 0; j < Ret->Count; j++)
    {
      if (nc_strcmp(Ret->Items[j]->Name, "default") == 0)
      {
        break;
      }
    }
    if (j == Ret->Count)
    {
      cprintf("\r\n\r\nError: Missing [default] Section!\r\n");
      goto error;
    }
    else
    {
      cprintf("OK!\r\n");
    }
  }

  /* Check Parser Result */
  if (KeyIgnored)
  {
    cprintf("Warning: Ignored unknown Options.\r\n");
  }

  /* Exit */
  goto exit;
error:
  ProfileList_Free(Ret);
  Ret = NULL;
exit:
  if (FIn)   fclose(FIn);
  if (FTemp) fclose(FTemp);
  return Ret;
}

/*
  Save Config File
*/
unsigned WriteConfigFile(struct ProfileList * List)
{
  FILE * FOut  = NULL;
  unsigned Ret = 0;
  unsigned j;

  /* Write Message */
  cprintf("\r\nSaving configuration...");

  if (remove(ConfigFileName[ConfigFileIndex]))
  {
    perror("\r\nError removin Config File:\r\n");
    goto exit;
  }

  /* Open Config File for writing */
  FOut = fopen(ConfigFileName[ConfigFileIndex], "w");
  if (!FOut)
  {
    perror("\r\n\r\nError opening Config File:\r\n");
    goto exit;
  }

  /* Write [DSx86] Section */
  if (fprintf(FOut,
    "[DSx86]\n"
    "LogFile=%s\n"
    "\n",
    List->LogFile
  ) < 0) goto writeerror;

  /* Write Sections */
  for (j = 0; j < List->Count; j++)
  {
    struct Profile * P;
    unsigned k;

    /* Get Section Record */
    P  = List->Items[j];

    /* Write Section Header */
    if (fprintf(FOut, "[%s]\n", P->Name) < 0) goto writeerror;

    /* Write Scale Mode */
    if (P->ScaleMode)
      if (fprintf(FOut, "ScreenScale=%s\n", Scale_Modes[P->ScaleMode]) < 0)
        goto writeerror;

    /* Write Update Mode */
    if (P->UpdateMode)
      if (fprintf(FOut, "ScreenUpdate=%s\n", Update_Modes[P->UpdateMode]) < 0)
        goto writeerror;

    /* Write Key Bindings */
    for (k = 0; k < NUM_NDSKEYS; k++)
    {
      /* Check Key */
      if (P->KeyFlags & (1 << k))
      {
        /* Write Key if present */
        if (P->ExtKeys[k])
        {
          /* Extended ScanCode */
          if (fprintf(
            FOut, "%s=%.2X%.2X\n", NDS_Keys[k], P->ExtKeys[k], P->Keys[k]) < 0)
              goto writeerror;
        }
        else
        {
          /* Normal ScanCode */
          if (fprintf(
            FOut, "%s=%.2X\n", NDS_Keys[k], P->Keys[k]) < 0)
              goto writeerror;
        }
      }
    }

    /* Write Touch Pad Mouse Options */
    if (P->TPMFlags & (1 << TPM_SCROLL))
      if (fprintf(FOut, "TPMScroll=%s\n", Boolean_Modes[P->TPMScroll]) < 0)
        goto writeerror;
    if (P->TPMFlags & (1 << TPM_TAP))
      if (fprintf(FOut, "TPMTap=%s\n", Boolean_Modes[P->TPMTap]) < 0)
        goto writeerror;
    if (P->TPMFlags & (1 << TPM_RIGHT))
      if (fprintf(FOut, "TPMRight=%s\n", NDS_Keys[P->TPMRight]) < 0)
        goto writeerror;
    if (P->TPMFlags & (1 << TPM_LEFT))
      if (fprintf(FOut, "TPMLeft=%s\n", NDS_Keys[P->TPMLeft]) < 0)
        goto writeerror;

    /* Append NewLine */
    if (fprintf(FOut, "\n") < 0)
        goto writeerror;
  }

  /* Return */
  cprintf("OK!\r\n");
  Ret = 1;
  goto exit;
writeerror:
  perror("\r\n\r\nError writing Config File:\r\n");
  Ret = 0;
exit:
  if (FOut)  fclose(FOut);
  return Ret;
}

/* End of File ****************************************************************/